<?php
// 工具类
namespace app\common\service;

use GuzzleHttp\Client;
use Exception;

class Util
{
    /**
     * 验证身份证号码是否正确并有效
     * 支持15位和18位身份证号码
     * @param string $idCard 身份证号码
     * @return bool 验证结果
     */
    public static function validateIdCard(string $idCard)
    {
        // 去除空格
        $idCard = trim($idCard);
        $length = strlen($idCard);

        // 1. 检查基本格式
        if ($length == 15) {
            // 15位身份证
            if (!preg_match('/^[1-9]\d{5}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/', $idCard)) {
                return false;
            }

            // 验证出生日期
            $year = '19' . substr($idCard, 6, 2);
            $month = substr($idCard, 8, 2);
            $day = substr($idCard, 10, 2);

            return self::isValidDate($year, $month, $day);
        } elseif ($length == 18) {
            // 18位身份证
            if (!preg_match('/^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}(\d|X|x)$/', $idCard)) {
                return false;
            }

            // 验证出生日期
            $year = substr($idCard, 6, 4);
            $month = substr($idCard, 10, 2);
            $day = substr($idCard, 12, 2);

            if (!self::isValidDate($year, $month, $day)) {
                return false;
            }

            // 2. 验证校验码
            $weight = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; // 加权因子
            $codes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']; // 校验码对应值

            $sum = 0;
            for ($i = 0; $i < 17; $i++) {
                $sum += substr($idCard, $i, 1) * $weight[$i];
            }

            $mod = $sum % 11;
            $checkCode = $codes[$mod];

            // 转换最后一位为大写X进行比较
            return strtoupper(substr($idCard, 17, 1)) === $checkCode;
        } else {
            // 长度错误
            return false;
        }
    }

    /**
     * 检查日期是否有效
     * @param int $year 年份
     * @param int $month 月份
     * @param int $day 日
     * @return bool 有效性结果
     */
    public static function isValidDate($year, $month, $day)
    {
        // 检查月份是否有效
        if ($month < 1 || $month > 12) {
            return false;
        }

        // 检查日期是否有效
        $daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

        // 处理闰年
        if ($month == 2 && (($year % 4 == 0 && $year % 100 != 0) || ($year % 400 == 0))) {
            $daysInMonth[1] = 29;
        }

        return $day >= 1 && $day <= $daysInMonth[$month - 1];
    }

    /**
     * 验证手机号码格式
     * @param string $mobile 手机号码
     * @return bool 验证结果
     */
    public static function validateMobile(string $mobile): bool
    {
        return preg_match('/^1[3-9]\d{9}$/', $mobile) === 1;
    }

    /**
     * 验证密码强度
     * 要求：至少8位，包含字母和数字
     * @param string $password 密码
     * @return bool 验证结果
     */
    public static function validatePassword(string $password): bool
    {
        return preg_match('/^(?=.*[A-Za-z])(?=.*\d).{8,}$/', $password) === 1;
    }

    /**
     * 将下划线命名的数组键转换为驼峰式命名
     * @param array $data 需要转换的数组
     * @return array 转换后的数组
     */
    public static function underscoreToCamelCase($data)
    {
        if (!is_array($data)) {
            return $data;
        }

        $result = [];
        foreach ($data as $key => $value) {
            // 将下划线转换为驼峰式
            $camelCaseKey = preg_replace_callback('/_([a-z])/', function ($matches) {
                return strtoupper($matches[1]);
            }, $key);

            // 递归处理子数组
            $result[$camelCaseKey] = is_array($value) ? self::underscoreToCamelCase($value) : $value;
        }

        return $result;
    }

    /**
     * 获取身份证中的出生日期
     * @param string $idCard 身份证号码
     * @return string|null 出生日期(YYYY-MM-DD)，如果身份证无效则返回null
     */
    public static function getBirthDateFromIdCard($idCard)
    {
        if (!self::validateIdCard($idCard)) {
            return null;
        }

        $year = substr($idCard, 6, 4);
        $month = substr($idCard, 10, 2);
        $day = substr($idCard, 12, 2);

        return $year . '-' . $month . '-' . $day;
    }

    /**
     * 获取身份证中的性别
     * @param string $idCard 身份证号码
     * @return string|null 性别(男/女)，如果身份证无效则返回null
     */
    public static function getGenderFromIdCard($idCard)
    {
        if (!self::validateIdCard($idCard)) {
            return null;
        }

        $genderCode = substr($idCard, 16, 1);
        return $genderCode % 2 === 0 ? '女' : '男';
    }

    /**
     * 获取身份证中的年龄
     * @param string $idCard 身份证号码
     * @return int|null 年龄，如果身份证无效则返回null
     */
    public static function getAge($idCard)
    {
        if (!self::validateIdCard($idCard)) {
            return null;
        }
        # 1.从身份证中获取出生日期
        $birth_Date = strtotime(self::getBirthDateFromIdCard($idCard)); //截取日期并转为时间戳
        if (!$birth_Date) {
            return null;
        }

        # 2.格式化[出生日期]
        $Year = date('Y', $birth_Date); //yyyy
        $Month = date('m', $birth_Date); //mm
        $Day = date('d', $birth_Date); //dd

        # 3.格式化[当前日期]
        $current_Y = date('Y'); //yyyy
        $current_M = date('m'); //mm
        $current_D = date('d'); //dd

        # 4.计算年龄()
        $age = $current_Y - $Year; //今年减去生日年
        if ($Month > $current_M || $Month == $current_M && $Day > $current_D) { //深层判断(日)
            $age--; //如果出生月大于当前月或出生月等于当前月但出生日大于当前日则减一岁
        }

        # 返回年龄
        return $age;
    }

    /**
     * ajax返回数据
     * @param int $code 状态码
     * @param string $msg 提示信息
     * @param array $data 数据
     * @return void
     */
    public static function ajaxReturn($code = 0, $msg = '', $data = [])
    {
        // 将数据中的下划线改为驼峰命名法
        $data = self::underscoreToCamelCase($data);

        $result = [
            'status' => $code,
            'msg' => $msg,
            'data' => $data,
        ];
        exit(json_encode($result));
    }

    /**
     * 合并多个空格为一个空格
     * @param $string
     * @return string
     */
    public static function mergeSpaces($string)
    {
        $string = str_replace(self::filterChars(), ' ', $string);
        return preg_replace("/\s(?=\s)/", "\\1", $string);
    }

    /**
     * 去除空格
     * @param $string
     * @return string
     */
    public static function removeSpaceReturn($string)
    {
        $string = str_replace(self::filterChars(), '', $string);    // 除去换行和空格
        $string = preg_replace('/[\x{200B}]/u', '', $string); //去除零宽不可见字符
        $string = trim($string);    // 除去两端空格
        return $string;
    }

    /**
     * 过滤字符
     * @return string[]
     */
    public static function filterChars()
    {
        return ["\r", "\n", " ", " ", "\t"];
    }

    /**
     * 格式化JSON字符串
     * @param string $str JSON字符串
     * @return string 格式化后的JSON字符串
     */
    public static function formatJsonStr($str)
    {
        $str = preg_replace("/^\[\"|\"\]$/", "", json_encode([$str]));
        return str_replace("\\", "\\\\", $str);
    }

    /**
     * 发送POST请求
     * 使用GuzzleHttp客户端发送请求
     * @param string $url 请求URL
     * @param array $postData 要发送的数据
     * @param array $headers 请求头
     * @param int $timeout 超时时间（秒）
     * @return array 响应数据
     * @throws Exception 当请求失败或响应格式错误时抛出异常
     */
    public static function sendPostRequest($url, $postData, $headers = [], $timeout = 30)
    {
        // 使用GuzzleHttp客户端发送请求
        $client = new Client();
        try {
            // 默认请求头
            $defaultHeaders = ['Content-Type' => 'application/json'];
            // 合并请求头
            $headers = array_merge($defaultHeaders, $headers);

            $response = $client->post($url, [
                'headers' => $headers,
                'body' => json_encode($postData),
                'timeout' => $timeout
            ]);
            $result = $response->getBody()->getContents();
            $data = json_decode($result, true);

            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new Exception('响应格式错误: ' . $result);
            }

            return $data;
        } catch (\GuzzleHttp\Exception\GuzzleException $e) {
            throw new Exception('请求失败: ' . $e->getMessage());
        }
    }

    /**
     * 发送GET请求
     * 使用GuzzleHttp客户端发送请求
     * @param string $url 请求URL
     * @param array $queryParams 查询参数
     * @param array $headers 请求头
     * @param int $timeout 超时时间（秒）
     * @return array 响应数据
     * @throws Exception 当请求失败或响应格式错误时抛出异常
     */
    public static function sendGetRequest($url, $queryParams = [], $headers = [], $timeout = 30)
    {
        $client = new Client();
        try {
            $defaultHeaders = ['Accept' => 'application/json'];
            $headers = array_merge($defaultHeaders, $headers);

            $response = $client->get($url, [
                'headers' => $headers,
                'query' => $queryParams,
                'timeout' => $timeout
            ]);
            $result = $response->getBody()->getContents();
            $data = json_decode($result, true);

            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new Exception('响应格式错误: ' . $result);
            }

            return $data;
        } catch (\GuzzleHttp\Exception\GuzzleException $e) {
            throw new Exception('请求失败: ' . $e->getMessage());
        }
    }

    /**
     * 数组层级缩进转换
     * @param array $array 源数组
     * @param int   $pid
     * @param int   $level
     * @return array
     */
    public static function array2level($array, $pid = 0, $level = 1)
    {
        static $list = [];
        foreach ($array as $v) {
            if ($v['pid'] == $pid) {
                $v['level'] = $level;
                $list[]     = $v;
                self::array2level($array, $v['id'], $level + 1);
            }
        }
        return $list;
    }

    /**
     * 构建层级（树状）数组
     * @param array  $array          要进行处理的一维数组，经过该函数处理后，该数组自动转为树状数组
     * @param string $pid_name       父级ID的字段名
     * @param string $child_key_name 子元素键名
     * @return array|bool
     */
    public static function array2tree(&$array, $pid_name = 'pid', $child_key_name = 'children')
    {
        $counter = self::array_children_count($array, $pid_name);
        if (!isset($counter[0]) || $counter[0] == 0) {
            return $array;
        }
        $tree = [];
        while (isset($counter[0]) && $counter[0] > 0) {
            $temp = array_shift($array);
            if (isset($counter[$temp['id']]) && $counter[$temp['id']] > 0) {
                array_push($array, $temp);
            } else {
                if ($temp[$pid_name] == 0) {
                    $tree[] = $temp;
                } else {
                    $array = self::array_child_append($array, $temp[$pid_name], $temp, $child_key_name);
                }
            }
            $counter = self::array_children_count($array, $pid_name);
        }
        return $tree;
    }

    /**
     * 子元素计数器
     * @param array $array
     * @param int   $pid
     * @return array
     */
    public static function array_children_count($array, $pid)
    {
        $counter = [];
        foreach ($array as $item) {
            $count = isset($counter[$item[$pid]]) ? $counter[$item[$pid]] : 0;
            $count++;
            $counter[$item[$pid]] = $count;
        }
        return $counter;
    }

    /**
     * 把元素插入到对应的父元素$child_key_name字段
     * @param        $parent
     * @param        $pid
     * @param        $child
     * @param string $child_key_name 子元素键名
     * @return mixed
     */
    public static function array_child_append($parent, $pid, $child, $child_key_name)
    {
        foreach ($parent as &$item) {
            if ($item['id'] == $pid) {
                if (!isset($item[$child_key_name])) {
                    $item[$child_key_name] = [];
                }

                $item[$child_key_name][] = $child;
            }
        }
        return $parent;
    }

    /**
     * 二维数组排序
     * @param array $data
     * @param $field
     * @return array
     */
    public static function ewArraySort(array $data, $field)
    {
        $sort = array_column($data, $field);
        array_multisort($sort, SORT_ASC, $data);
        return $data;
    }
}
